/*
 *    Copyright 2008-2009 The Digester Tools Team
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.digestertools.annotations.handlers;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.apache.commons.digester.Rule;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.digestertools.annotations.AnnotationHandler;
import com.digestertools.annotations.AnnotationRuleProvider;
import com.digestertools.annotations.AnnotationsContext;
import com.digestertools.annotations.CreationRule;
import com.digestertools.annotations.DigesterLoader;
import com.digestertools.annotations.DigesterRule;
import com.digestertools.annotations.FromAnnotationsRuleSet;
import com.digestertools.annotations.utils.AnnotationUtils;

/**
 * 
 * @author Simone Tripodi
 * @version $Id: AbstractMethodHandler.java 357 2009-12-16 20:00:36Z simone.tripodi $
 */
abstract class AbstractMethodHandler<A extends Annotation> implements AnnotationHandler<A, Method> {

    private final Log log = LogFactory.getLog(this.getClass());

    public void handle(A annotation, Method target, AnnotationsContext context, FromAnnotationsRuleSet ruleSet) {
        if (target.getParameterTypes().length != 1) {
            DigesterRule rule = (DigesterRule) annotation.annotationType().getAnnotation(DigesterRule.class);
            Class<? extends Rule> reflectedRule = rule.reflectsRule();

            if (this.log.isWarnEnabled()) {
                this.log.warn("Digester Rule '"
                    + reflectedRule.getName()
                    + "' just support one argument");
            }
        }

        String methodName = target.getName();
        Object explicitTypesObject = AnnotationUtils.getAnnotationValue(annotation);
        Class<?>[] explicitTypes = null;
        if (explicitTypesObject != null) {
            explicitTypes = (Class<?>[]) explicitTypesObject;
        }
        Class<?> paramType = target.getParameterTypes()[0];

        if (explicitTypes != null && explicitTypes.length > 0) {
            for (Class<?> explicitType : explicitTypes) {
                if (paramType.isAssignableFrom(explicitType)) {
                    this.createRuleProvider(methodName, explicitType, ruleSet);
                } else if (this.log.isWarnEnabled()) {
                    this.log.warn("Explicit parameter type '"
                            + explicitType.getName()
                            + "' doesn't extend/implement argument type '"
                            + paramType.getName()
                            + "', ignoring it");
                }
            }
        } else if (!paramType.isInterface()
                && !Modifier.isAbstract(paramType.getModifiers())) {
            this.createRuleProvider(methodName, paramType, ruleSet);
        } else if (this.log.isWarnEnabled()) {
            this.log.warn("Impossible to analyze '"
                    + paramType.getName()
                    + "' since is abstract or is an interface");
        }
    }

    private void createRuleProvider(String methodName,
            Class<?> paramType,
            FromAnnotationsRuleSet ruleSet) {
        for (Annotation annotation : paramType.getAnnotations()) {
            this.handle(annotation, methodName, paramType, ruleSet);
        }
    }

    private void handle(Annotation annotation,
            String methodName,
            Class<?> paramType,
            FromAnnotationsRuleSet ruleSet) {
        if (annotation.annotationType().isAnnotationPresent(DigesterRule.class)
                && annotation.annotationType().isAnnotationPresent(CreationRule.class)) {
            // inspect the target type first
            FromAnnotationsRuleSet derivedRuleSet = (FromAnnotationsRuleSet) DigesterLoader.getRuleSet(paramType);
            ruleSet.addRules(derivedRuleSet);

            // build the rule
            String pattern = (String) AnnotationUtils.getAnnotationPattern(annotation);
            ruleSet.addRuleProvider(pattern, this.newRuleProvider(methodName, paramType.getName()));
        } else {
            // check if it is one of the *.List annotation
            Annotation[] annotations = AnnotationUtils.getAnnotationsArrayValue(annotation);
            if (annotations != null) {
                // if it is an annotations array, process them
                for (Annotation ptr : annotations) {
                    this.handle(ptr, methodName, paramType, ruleSet);
                }
            }
        }
    }

    protected abstract AnnotationRuleProvider<? extends Rule> newRuleProvider(String methodName, String paramType);

}
